package com.nage.components.collision;

import java.util.ArrayList;

import android.util.Log;

import com.nage.components.Component;
import com.nage.engine.GameObject;

/**
 * CollisionManager is a component of a game object that contains references to
 * all Collidable objects, and will check them for any collisions and call the
 * correct methods. Only one instance of CollisionManager should be allowed - singleton.
 * @author lukeo05
 *
 */
public class CollisionManager extends Component {
	
	/**
	 * Contains the registered game objects that are all collidable and therefore
	 * need to be tested for collisions regularly.
	 */
	static ArrayList<Collidable> collidables;
	
	/**
	 * Contains a list of all the current active collisions between two collidables.
	 */
	ArrayList<Collision> collisions;

	private ArrayList<Collision> collisionsToAdd;
	private ArrayList<Collision> collisionsToRemove;
	
	private static boolean instantiated;
	private static CollisionManager instance;

	private CollisionManager(GameObject p) {
		super(p);
		type = COLLISION_MANAGER;
		instantiated = true;
		collidables = new ArrayList<Collidable>();
		collisions = new ArrayList<Collision>();
		collisionsToRemove = new ArrayList<Collision>();
		collisionsToAdd = new ArrayList<Collision>();
	}

	/**
	 * Performs the frame by frame collision detection.
	 */
	@Override
	public void update(long delta) {
		
		if(!collisionsToRemove.isEmpty()) {
			for (Collision c : collisionsToRemove) {
				collisions.remove(c);
			}
			collisionsToRemove.clear();
		}
		if(!collisionsToAdd.isEmpty()) {
			for (Collision c : collisionsToAdd) {
				collisions.add(c);
			}
			collisionsToAdd.clear();
		}
		
		for(Collidable c : collidables) {
			for(Collidable d : collidables) {
				if(!c.equals(d)) {
					// Checking each collidable against every other collidable that isn't itself.
					if(checkCollision(c.mask, d.mask) || checkCollision(d.mask, c.mask)) {
						if(!collided(c, d)) {
							// c and d are overlapping and there collision has not been recorded
							// in the collisions table. notify them and record collisions.
							Log.d("CollisionManager", "Collision between "+c.parent.m_Name+" & "+d.parent.m_Name);
							Log.d("CollisionManager", "Calling onCollision for "+c.toString()+" and "+d.toString());
							Collision col = new Collision(c, d);
							c.onCollision(col);
							d.onCollision(col);
							addCollision(col);
							return;
						}
					} else {
						if(collided(c, d)) {
							// c and d are not overlapping, but they are recorded as collided. 
							// notify them they have seperated and remove from table.
							Log.d("CollisionManager", "Seperation between "+c.parent.m_Name+" & "+d.parent.m_Name);
							c.onSeperation();
							d.onSeperation();
							removeCollision(c, d);
						}
					}
				}
			}
		}
	}
	
	private void removeCollision(Collidable c, Collidable d) {
		for(Collision collision : collisions) {
			if (collision.isBetween(c, d))  {
				collisionsToRemove.add(collision);
			}
		}
	}

	private void addCollision(Collision c) {
		collisionsToAdd.add(c);
	}

	private boolean collided(Collidable c, Collidable d) {
		for(Collision collision : collisions) {
			if (collision.isBetween(c, d)) return true;
		}
		return false;
	}

	/**
	 * Checks each corner of b to see if it is inside a. If any test is true,
	 * then a collision has occured.
	 * @param a		CollisionMask of first object to be tested
	 * @param b		CollisionMask of second object to be tested
	 * @return		true if a collision has occured
	 */
	private boolean checkCollision(CollisionMask a, CollisionMask b) {
		
		return(	a.isInside(b.left, b.top)
			||	a.isInside(b.right, b.top)
			||	a.isInside(b.left, b.bottom)
			||	a.isInside(b.right, b.bottom));
	}

	/** 
	 * Registers a Collidable with the manager so that it will start checking for collisions.
	 * 
	 * @param c This collidable that is to be added to list of registered objects.
	 */
	public static void register(Collidable c) {
		collidables.add(c);
	}

	/**
	 * Static method to create an instance of CollisionManager. This is stored in
	 * the static field instance. If you try to create a new CollisionManager and
	 * one already exists, the instance will be overwritten. This is because
	 * static fields can remain between Activity destructions, so we need to make
	 * sure we overwrite the old one when starting up the activity.
	 * 
	 * @param c		The game object that this CollisionManager will be a component of.
	 * @return		An instance of CollisionManager
	 * @throws Exception	When a CollisionManager is already instantiated.
	 */
	public static CollisionManager newCollisionManager(GameObject c) {
			instance = new CollisionManager(c);
			return instance;
	}
	
	/**
	 * Static method used to get an instance of CollisionManager if one has already
	 * been instantiated.
	 * 
	 * @return		Existing instance of CollisionManager
	 * @throws Exception	If no CollisionManager exists.
	 */
	public static CollisionManager getCollisionManager() throws Exception {
		if(instantiated) {
			return instance;
		} else {
			throw new Exception("No CollisionManager exists.");
		}
	}

}
